Skip to content

feat(io)!: multishot receive into the runtime's provided buffer ring - #149

Merged
lexoliu merged 2 commits into
mainfrom
feat/124-multishot-recv
Nov 11, 2026
Merged

lexoliu merged 2 commits into
mainfrom
feat/124-multishot-recv

Conversation

@lexoliu

@lexoliu lexoliu commented Nov 11, 2026

Copy link
Copy Markdown
Collaborator

On Linux, a compio stream now receives with one multishot receive into the runtime's provided buffer ring. One submission serves many reads, and a connection holds a buffer only from when bytes arrive until its reader is done with them.

  • ReadOwned::read_owned lends the ring buffer (Received::Lent); dropping it returns the buffer. Streams with no ring (tokio, async-io, TLS halves) and an exhausted ring answer ReadInstead, which takes the plain read path. h1 receives through it.
  • ReadAhead::stop_read_ahead cancels and drains the armed receive. The kernel-TLS switch calls it so the bytes read past the last handshake record go to rustls instead of being lost, since the kernel can only decrypt what is still in the socket.
  • Splice moves bytes already received into the pipe first, then cancels the receive, and only then splices from the socket, so it never skips over received data.
  • StreamSocket now also requires ReadOwned + ReadAhead.

This uses compio's own buffer pool, one per runtime and so one per serving thread. It is correct without incremental consumption (IOU_PBUF_RING_INC). Incremental consumption needs IORING_CQE_F_BUF_MORE, which compio does not expose yet (compio-rs/compio#1142). compio's default pool is 8 × 8 KiB, so servers have to size it on their runtime builder.

New io tests run each read through a small ring and fail if it falls back. They cover one receive submission serving 64 KiB of reads, a connection reading more than the ring holds, a splice taking what was already received first, and stopping read-ahead. On Linux the io, h1 and default tls suites pass. The kernel-TLS switch needs the tls module, so only CI exercises it.

Closes #124

On Linux over io_uring a compio stream now receives with one armed
multishot receive into the runtime's buffer pool instead of a submission
per read. Received bytes are lent through the new `ReadOwned` trait or
copied into the caller's buffer by `Read`, and each ring buffer goes back
as soon as its bytes are consumed, so an idle connection holds none. A read
that finds the ring dry receives into the caller's buffer and the next one
arms again.

A splice from the socket first moves the bytes already received, and
`ReadAhead::stop_read_ahead` ends the receive for good and hands those
bytes back; the kernel TLS switch uses it so the kernel starts at the
right record. `StreamSocket` now requires both traits.

Buffers are consumed whole: incremental consumption (IOU_PBUF_RING_INC)
needs IORING_CQE_F_BUF_MORE, which compio does not report.
A connection's receive half is now `ReadOwned`. Bytes a stream lends are
copied onto the end of the receive buffer and the stream's buffer goes
back at once, so a connection reserves room only for bytes that arrived;
a stream that lends nothing is read into the buffer's spare capacity as
before.
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 88.46154% with 27 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
io/src/compio.rs 89.07% 20 Missing ⚠️
tls/src/stream.rs 37.50% 5 Missing ⚠️
h1/src/conn.rs 80.00% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@lexoliu
lexoliu merged commit 2bf63aa into main Nov 11, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

io: multishot accept and receive over a provided buffer ring on io_uring

2 participants